package modelo.policiales;

import java.io.File;
import java.util.HashMap;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import observadores.Observable;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import modelo.delictivas.ITrampa;
import modelo.delictivas.Robo;
import modelo.geografia.Capital;
import modelo.geografia.Coordenada;
import modelo.geografia.Mapa;
import modelo.geografia.Viaje;
import modelo.lugaresYPistas.Establecimiento;
import modelo.lugaresYPistas.IRespuesta;
import modelo.reduccionDeTiempo.Herida;
import modelo.reduccionDeTiempo.Necesidad;

public class Policia extends Observable{

	private Rango rango;
	private String nombre;
	private Capital capitalActual;
	private Investigacion investigacion;
	private int horasDespierto;
	private HashMap<Establecimiento, Integer> diccionarioVisitasAEstablecimiento;
	private IEmisorOrdenesArresto emisorOrdenArresto;
	private String nombreCapitalPorDefecto = "SinCapital";
	
	
	private Policia(String nombre, Rango rango){
		
		this.rango = rango;
		this.nombre = nombre;
		this.horasDespierto = 0;
		this.diccionarioVisitasAEstablecimiento = new HashMap<Establecimiento, Integer>();
		this.setCapitalActual( new Capital ( this.nombreCapitalPorDefecto, new Coordenada(0,0) ) );
	}
	
	public Policia(String nombre) {
		this.rango = RangoNovato.obtener();
		this.nombre = nombre;
		this.horasDespierto = 0;
		this.setCapitalActual( new Capital ( this.nombreCapitalPorDefecto, new Coordenada(0,0) ) );
		this.diccionarioVisitasAEstablecimiento = new HashMap<Establecimiento, Integer>();
		
	}

	
	public Rango getRango() {
		return this.rango;	
	}

	public void setRango(Rango unRango) {
		
		this.rango = unRango;
	}

	public int getVelocidadDeViaje() {
		return this.rango.getVelocidadDeViaje();
	}

	public String getNombre() {
		return this.nombre;
	}
	
	public int getTiempoDespierto(){
		
		return this.horasDespierto;
	}

	public Capital getCapitalActual() {

		return this.capitalActual;
	}

	public Investigacion getInvestigacion() {
		return this.investigacion;
	}

	public void crearInvestigacion(int tiempoDisponible, Robo robo) throws Exception {
		
		this.investigacion = new Investigacion(tiempoDisponible, robo);
		this.capitalActual = robo.getLugarDelRobo();
		this.diccionarioVisitasAEstablecimiento = new HashMap<Establecimiento, Integer>();
		
	}
	
	public IRespuesta pedirRespuestaEn(Establecimiento unEstablecimiento) {
		
		this.visitarEstablecimiento(unEstablecimiento);
		IRespuesta respuesta = unEstablecimiento.darRespuestaA(this);
		if(!this.tiempoAgotado())
			this.notificarRespuestaRecibida(respuesta);
		return respuesta;
	}

	public void caerEnTrampa(ITrampa trampa) {
		
		Herida herida = trampa.accionarTrampa();
		this.estarDespiertoDurante(herida.getTiempoPerdido());
		if(!this.tiempoAgotado())
			this.notificarTrampa(trampa);
	}

	private void visitarEstablecimiento(Establecimiento unEstablecimiento) {
		
		this.diccionarioVisitasAEstablecimiento.put(unEstablecimiento, this.obtenerCantidadVisitasAEstablecimiento(unEstablecimiento)+1);		
		this.estarDespiertoDurante(this.obtenerHorasConsumidasParaVisitarEstablecimiento(unEstablecimiento));
	}

	private int obtenerHorasConsumidasParaVisitarEstablecimiento(Establecimiento unEstablecimiento) {
		
		//Una hora por cada visita realizada.
		return this.diccionarioVisitasAEstablecimiento.get(unEstablecimiento).intValue();
	}

	public void viajar(Capital unaCapital) throws Exception {
		
		if(this.investigacion == null)
			throw new Exception("Debe crearse una investigacion para viajar");
		
		Viaje unViaje = new Viaje(this.capitalActual, unaCapital, this.rango.velocidadDeViaje);
		this.investigacion.agregarViaje(unViaje);
		
		this.estarDespiertoDurante(unViaje.getTiempoDeViaje());
		
		this.capitalActual = unaCapital;
		
		if(!this.tiempoAgotado())
			this.notificarViaje();
	}
	
	private void estarDespiertoDurante (int tiempoDespierto){
		
		this.horasDespierto += tiempoDespierto;
		this.reducirTiempoAInvestigacion(tiempoDespierto);
		this.verificarSuenio();
		
	}

	private void reducirTiempoAInvestigacion(int tiempo) {
		this.investigacion.reducirTiempo(tiempo);
		if(this.tiempoAgotado())
		{
			this.cerrarInvestigacion();
			this.notificarTiempoTerminado();
		}
	}

	private void verificarSuenio() {
		
		if( this.necesitaDormir() )
			this.dormir();	
	}

	private void dormir() {
		this.reducirTiempoAInvestigacion(Necesidad.dormir.getTiempoPerdido());
		this.horasDespierto = 0;	
		if(!this.tiempoAgotado())
			this.notificarPoliciaDuerme();
	}

	private boolean necesitaDormir() {
		
		int tiempoMaximoDespierto = 16; 
		
		if (this.horasDespierto >= tiempoMaximoDespierto)
			return true;
		return false;
	}

	public int obtenerCantidadVisitasAEstablecimiento(Establecimiento establecimiento) {
		
		if(this.diccionarioVisitasAEstablecimiento.containsKey(establecimiento))
			return this.diccionarioVisitasAEstablecimiento.get(establecimiento).intValue();
		else
			return 0;
	}

	public IRespuesta perseguirLadron() {
		
		if(this.investigacion.puedeRealizarseArresto())
			this.arrestarLadron();
		
		this.notificarLadronEncontrado();
		this.cerrarInvestigacion();
		return new EncuentroConLadron(this);
	}

	private void arrestarLadron() {
		this.investigacion.asentarArrestoDeLadron();		
	}

	private void cerrarInvestigacion() {
		this.investigacion.cerrar();
		if(this.investigacion.ladronFueArrestado())
			this.notificarLadronArrestado();
		else if(!this.tiempoAgotado())
			this.notificarLadronEscapa();
	}
	
	private boolean tiempoAgotado() {
		return this.getInvestigacion().getTiempoDisponible() <= 0;
	}

	private void setInvestigacion(Investigacion unaInvestigacion){
		
		this.investigacion = unaInvestigacion;
	}

	private void setHorasDespierto(int horas){
		
		this.horasDespierto = horas;	
	}
	
	private void setCapitalActual(Capital capital){
		
		if (capital == null)
			this.capitalActual = new Capital (this.nombreCapitalPorDefecto , new Coordenada(0,0));
		else
			this.capitalActual = capital;
		
	}

	public OrdenArresto pedirOrdenArresto() {
		
		OrdenArresto orden = this.emisorOrdenArresto.emitirOrdenDeArresto(this.getInvestigacion().getDatosLadron().getSexo()
				, this.getInvestigacion().getDatosLadron().getCabello(), this.getInvestigacion().getDatosLadron().getVehiculo()
				, this.getInvestigacion().getDatosLadron().getSenia(), this.getInvestigacion().getDatosLadron().getHobby());
		if (this.investigacion != null)
			this.investigacion.setOrdenArresto(orden);
		
		this.reducirTiempoAInvestigacion(OrdenArresto.getTiempoConsumidoAlPedir());
		
		if(orden == null)
			this.notificarOrdenDeArrestoDenegada();
		else
			this.notificarOrdenDeArrestoObtenida(orden.getLadronBuscado());
		
		return orden;
	}

	public IEmisorOrdenesArresto getEmisorOrdenDeArresto() {
		return this.emisorOrdenArresto;
	}

	public void setEmisorOrdenArresto(IEmisorOrdenesArresto emisor) {
		this.emisorOrdenArresto = emisor;
	}
	
	public Node serializar(Document doc) throws ParserConfigurationException, TransformerException {
		
		Element elementoPolicia = doc.createElement("Policia");
		elementoPolicia.setAttribute("Nombre", this.nombre);
		elementoPolicia.setAttribute( "Rango", this.rango.obtenerNombreDelRango() );
		elementoPolicia.setAttribute( "HorasDespierto", Integer.toString(this.horasDespierto) ) ;
		Element elementoCapitalActual = doc.createElement("CapitalActual");
		elementoCapitalActual.appendChild(this.capitalActual.serializar(doc));
		elementoPolicia.appendChild(elementoCapitalActual);
		
		if( this.investigacion != null ){
			this.investigacion.guardar(this.getNombre());
		}
		else{
			elementoPolicia.setAttribute("Investigacion", "No tiene");
		}
		
		Element elementoVisitas = doc.createElement("Visitas");
		
		if(this.capitalActual.getCantidadDeEstablecimientos() == 3){
			for (int i = 0 ; i < 3 ; i ++){
				Element elementoEstablecimientoVisitado = doc.createElement("EstablecimientoVisitado");
				Establecimiento est = this.capitalActual.getEstablecimientos().get(i);
				Integer vecesVisitado;
				if( diccionarioVisitasAEstablecimiento.containsKey(est) ){
						vecesVisitado = this.diccionarioVisitasAEstablecimiento.get(est).intValue();
				}
				else {
						vecesVisitado = 0;
				}
				elementoEstablecimientoVisitado.setAttribute("nombre", est.getNombre());
				elementoEstablecimientoVisitado.setAttribute("vecesVisitado", vecesVisitado.toString());
				elementoVisitas.appendChild(elementoEstablecimientoVisitado);
			}
		}
		
		elementoPolicia.appendChild(elementoVisitas);
		
		return elementoPolicia; 
	}
	
	public void cargarInvestigacion() throws Exception{
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		Document doc = dBuilder.newDocument();
		
		File archivo = new File("Investigacion"+this.getNombre()+".xml");
		
		if(!archivo.exists())
		{
			if(this.getInvestigacion() != null)
				this.getInvestigacion().guardar(this.getNombre());
			else
				throw new Exception("El policia no tiene investigacion para cargar");
		}
		
		doc = dBuilder.parse(archivo);
		doc.getDocumentElement().normalize();
		
		Element elementoInvestigacion = (Element) doc.getElementsByTagName("Investigacion").item(0);

		this.setInvestigacion(Investigacion.hidratar(elementoInvestigacion));
	}

	public static Policia hidratar(Element elementoPolicia) throws Exception {
			
		String nombre = ((Element)elementoPolicia).getAttribute("Nombre");
		String nombreRango = ((Element)elementoPolicia).getAttribute("Rango");
		
		Rango rangoNuevo= Rango.obtenerRangoSegunNombre(nombreRango);
		
		if (rangoNuevo == null)
			//Es casi imposible ya que se lee del archivo, pero no esta de mas y asegura
			new Exception ("Rango invalido");
		
		int horasDespierto = Integer.parseInt(((Element)elementoPolicia).getAttribute("HorasDespierto"));
		Element elementoCapital = (Element) elementoPolicia.getElementsByTagName("CapitalActual").item(0);
		
		Element elementoCapitalActual = (Element) elementoCapital.getElementsByTagName("Capital").item(0);
		Capital origen = Mapa.obtenerMapa().obtenerCapitalLlamada(elementoCapitalActual.getAttribute("nombre"));
		
		//Capital origen = Capital.hidratar ( (Element) elementoCapital.getElementsByTagName("Capital").item(0) );
		
		Policia nuevoPolicia = new Policia (nombre,rangoNuevo);
		
		nuevoPolicia.setHorasDespierto(horasDespierto);	
		nuevoPolicia.setCapitalActual(origen);
		
		HashMap<Establecimiento , Integer> hashMap = new HashMap<Establecimiento,Integer>();
		if(elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().getLength() == 3 &&
				origen.getCantidadDeEstablecimientos() == 3){
		//negrada elevada al cuadrado que no hay otra manera de resolver con los metodos actuales----------------------
					List<Establecimiento> establecimientos = origen.getEstablecimientos();
					Establecimiento establecimiento0 = establecimientos.get(0);
					Establecimiento establecimiento1 = establecimientos.get(1);
					Establecimiento establecimiento2 = establecimientos.get(2);
					for(int i = 0 ; i < elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().getLength() ; i++){
						if (elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().item(i).getAttributes().getNamedItem("nombre").getNodeValue().equals(establecimiento0.getNombre())){
							hashMap.put(establecimiento0 ,Integer.parseInt(elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().item(i).getAttributes().getNamedItem("vecesVisitado").getNodeValue()) );
						}
						if (elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().item(i).getAttributes().getNamedItem("nombre").getNodeValue().equals(establecimiento1.getNombre())){
							hashMap.put(establecimiento1 ,Integer.parseInt(elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().item(i).getAttributes().getNamedItem("vecesVisitado").getNodeValue()) );
						}
						if (elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().item(i).getAttributes().getNamedItem("nombre").getNodeValue().equals(establecimiento2.getNombre())){
							hashMap.put(establecimiento2 ,Integer.parseInt(elementoPolicia.getElementsByTagName("Visitas").item(0).getChildNodes().item(i).getAttributes().getNamedItem("vecesVisitado").getNodeValue()) );
						}
					}
		}
		//--------------------------------------------------------------------------------------------------------------
		nuevoPolicia.setHashMap(hashMap);
		
		return nuevoPolicia;
	}

	private void setHashMap(HashMap<Establecimiento, Integer> hashMap) {
		this.diccionarioVisitasAEstablecimiento = hashMap;
		
	}

	public boolean tieneInvestigacion() {
		File file = new File("Investigacion"+this.getNombre()+".xml");
		return file.exists();
	}

	
}
